/**
 * @ignore
 * dom iterator implementation using walker and nextSourceNode
 * @author yiminghe@gmail.com
 */
/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
 */

var util = require('util');
var $ = require('node');
var Walker = require('./walker');
var KERange = require('./range');
var Editor = require('./base');
var ElementPath = require('./element-path');
var TRUE = true,
    FALSE = false,
    NULL = null,
    UA = require('ua'),
    KER = Editor.RangeType,
    Dom = require('dom');

/**
 * iterator for range
 * @class KISSY.Editor.Iterator
 * @param range {KISSY.Editor.Range}
 * @private
 */
function Iterator(range) {
    if (arguments.length < 1) {
        return;
    }
    var self = this;
    self.range = range;
    self.forceBrBreak = FALSE;

    // Whether include <br>s into the enlarged range.(#3730).
    self.enlargeBr = TRUE;
    self.enforceRealBlocks = FALSE;

    self._ = self._ || {};
}

var beginWhitespaceRegex = /^[\r\n\t ]*$/;///^[\r\n\t ]+$/,//+:*??不匹配空串

util.augment(Iterator, {
    //奇怪点：
    //<ul>
    // <li>
    // x
    // </li>
    // <li>
    // y
    // </li>
    // </ul>
    //会返回两次 li,li,而不是一次 ul ，
    // 可能只是返回包含文字的段落概念？
    getNextParagraph: function (blockTag) {
        // The block element to be returned.
        var block,
            lastNode,
            self = this;

        // The range object used to identify the paragraph contents.
        var range;

        // Indicats that the current element in the loop is the last one.
        var isLast;

        // Instructs to cleanup remaining BRs.
        var removePreviousBr, removeLastBr;

        // self is the first iteration. Let's initialize it.
        if (!self._.lastNode) {
            range = self.range.clone();

            // 2010-09-30 shrink
            // 3.4.2 新增，
            // Shrink the range to exclude harmful "noises" (#4087, #4450, #5435).
            range.shrink(KER.SHRINK_ELEMENT, TRUE);

            range.enlarge(self.forceBrBreak || !self.enlargeBr ?
                KER.ENLARGE_LIST_ITEM_CONTENTS : KER.ENLARGE_BLOCK_CONTENTS);

            var walker = new Walker(range),
                ignoreBookmarkTextEvaluator = Walker.bookmark(TRUE, TRUE);
            // Avoid anchor inside bookmark inner text.
            walker.evaluator = ignoreBookmarkTextEvaluator;
            self._.nextNode = walker.next();
            // TODO: It's better to have walker.reset() used here.
            walker = new Walker(range);
            walker.evaluator = ignoreBookmarkTextEvaluator;
            lastNode = walker.previous();
            self._.lastNode = lastNode._4eNextSourceNode(TRUE);

            // We may have an empty text node at the end of block due to [3770].
            // If that node is the lastNode, it would cause our logic to leak to the
            // next block.(#3887)
            if (self._.lastNode &&
                self._.lastNode[0].nodeType === Dom.NodeType.TEXT_NODE && !util.trim(self._.lastNode[0].nodeValue) &&
                self._.lastNode.parent()._4eIsBlockBoundary()) {
                var testRange = new KERange(range.document);
                testRange.moveToPosition(self._.lastNode, KER.POSITION_AFTER_END);
                if (testRange.checkEndOfBlock()) {
                    var path = new ElementPath(testRange.endContainer);
                    var lastBlock = path.block || path.blockLimit;
                    self._.lastNode = lastBlock._4eNextSourceNode(TRUE);
                }
            }

            // Probably the document end is reached, we need a marker node.
            if (!self._.lastNode) {
                self._.lastNode = self._.docEndMarker = $(range.document.createTextNode(''));
                Dom.insertAfter(self._.lastNode[0], lastNode[0]);
            }

            // Let's reuse self variable.
            range = NULL;
        }

        var currentNode = self._.nextNode;
        lastNode = self._.lastNode;

        self._.nextNode = NULL;
        while (currentNode) {
            // closeRange indicates that a paragraph boundary has been found,
            // so the range can be closed.
            var closeRange = FALSE;

            // includeNode indicates that the current node is good to be part
            // of the range. By default, any non-element node is ok for it.
            var includeNode = (currentNode[0].nodeType !== Dom.NodeType.ELEMENT_NODE),
                continueFromSibling = FALSE;

            // If it is an element node, let's check if it can be part of the
            // range.
            if (!includeNode) {
                var nodeName = currentNode.nodeName();
                var forceBrBreak = self.forceBrBreak && {
                    br: 1
                };
                if (currentNode._4eIsBlockBoundary(forceBrBreak)) {
                    // <br> boundaries must be part of the range. It will
                    // happen only if ForceBrBreak.
                    if (nodeName === 'br') {
                        includeNode = TRUE;
                    } else if (!range && !currentNode[0].childNodes.length && nodeName !== 'hr') {
                        // If we have found an empty block, and haven't started
                        // the range yet, it means we must return self block.
                        block = currentNode;
                        isLast = currentNode.equals(lastNode);
                        break;
                    }

                    // The range must finish right before the boundary,
                    // including possibly skipped empty spaces. (#1603)
                    if (range) {
                        range.setEndAt(currentNode, KER.POSITION_BEFORE_START);

                        // The found boundary must be set as the next one at self
                        // point. (#1717)
                        if (nodeName !== 'br') {
                            self._.nextNode = currentNode;
                        }
                    }

                    closeRange = TRUE;
                } else {
                    // If we have child nodes, let's check them.
                    if (currentNode[0].firstChild) {
                        // If we don't have a range yet, let's start it.
                        if (!range) {
                            range = new KERange(self.range.document);
                            range.setStartAt(currentNode, KER.POSITION_BEFORE_START);
                        }

                        currentNode = $(currentNode[0].firstChild);
                        continue;
                    }
                    includeNode = TRUE;
                }
            } else if (currentNode[0].nodeType === Dom.NodeType.TEXT_NODE) {
                // Ignore normal whitespaces (i.e. not including &nbsp; or
                // other unicode whitespaces) before/after a block node.
                if (beginWhitespaceRegex.test(currentNode[0].nodeValue)) {
                    includeNode = FALSE;
                }
            }

            // The current node is good to be part of the range and we are
            // starting a new range, initialize it first.
            if (includeNode && !range) {
                range = new KERange(self.range.document);
                range.setStartAt(currentNode, KER.POSITION_BEFORE_START);
            }

            // The last node has been found.
            isLast = (!closeRange || includeNode) && currentNode.equals(lastNode);

            // If we are in an element boundary, let's check if it is time
            // to close the range, otherwise we include the parent within it.
            if (range && !closeRange) {
                while (!currentNode[0].nextSibling && !isLast) {
                    var parentNode = currentNode.parent();

                    if (parentNode._4eIsBlockBoundary(self.forceBrBreak && {br: 1})) {
                        closeRange = TRUE;
                        isLast = isLast || parentNode.equals(lastNode);
                        break;
                    }

                    currentNode = parentNode;
                    includeNode = TRUE;
                    isLast = currentNode.equals(lastNode);
                    continueFromSibling = TRUE;
                }
            }

            // Now finally include the node.
            if (includeNode) {
                range.setEndAt(currentNode, KER.POSITION_AFTER_END);
            }

            currentNode = currentNode._4eNextSourceNode(continueFromSibling, NULL, lastNode);
            isLast = !currentNode;

            // We have found a block boundary. Let's close the range and move out of the
            // loop.
            if (isLast || (closeRange && range)) {
                break;
            }
        }

        // Now, based on the processed range, look for (or create) the block to be returned.
        if (!block) {
            // If no range has been found, self is the end.
            if (!range) {
                if (self._.docEndMarker) {
                    self._.docEndMarker._4eRemove();
                }
                self._.nextNode = NULL;
                return NULL;
            }

            var startPath = new ElementPath(range.startContainer);
            var startBlockLimit = startPath.blockLimit,
                checkLimits = {div: 1, th: 1, td: 1};
            block = startPath.block;

            if ((!block || !block[0]) && !self.enforceRealBlocks &&
                checkLimits[ startBlockLimit.nodeName() ] &&
                range.checkStartOfBlock() &&
                range.checkEndOfBlock()) {
                block = startBlockLimit;
            } else if (!block || (self.enforceRealBlocks && block.nodeName() === 'li')) {
                // Create the fixed block.
                block = $(self.range.document.createElement(blockTag || 'p'));
                // Move the contents of the temporary range to the fixed block.
                block[0].appendChild(range.extractContents());
                block._4eTrim();
                // Insert the fixed block into the Dom.
                range.insertNode(block);
                removePreviousBr = removeLastBr = TRUE;
            } else if (block.nodeName() !== 'li') {
                // If the range doesn't includes the entire contents of the
                // block, we must split it, isolating the range in a dedicated
                // block.
                if (!range.checkStartOfBlock() || !range.checkEndOfBlock()) {
                    // The resulting block will be a clone of the current one.
                    block = block.clone(FALSE);

                    // Extract the range contents, moving it to the new block.
                    block[0].appendChild(range.extractContents());
                    block._4eTrim();

                    // Split the block. At self point, the range will be in the
                    // right position for our intents.
                    var splitInfo = range.splitBlock();

                    removePreviousBr = !splitInfo.wasStartOfBlock;
                    removeLastBr = !splitInfo.wasEndOfBlock;

                    // Insert the new block into the Dom.
                    range.insertNode(block);
                }
            } else if (!isLast) {
                // LIs are returned as is, with all their children (due to the
                // nested lists). But, the next node is the node right after
                // the current range, which could be an <li> child (nested
                // lists) or the next sibling <li>.

                self._.nextNode = (block.equals(lastNode) ? NULL :
                    range.getBoundaryNodes().endNode._4eNextSourceNode(TRUE, NULL, lastNode));
            }
        }

        if (removePreviousBr) {
            var previousSibling = $(block[0].previousSibling);
            if (previousSibling[0] && previousSibling[0].nodeType === Dom.NodeType.ELEMENT_NODE) {
                if (previousSibling.nodeName() === 'br') {
                    previousSibling._4eRemove();
                } else if (previousSibling[0].lastChild && Dom.nodeName(previousSibling[0].lastChild) === 'br') {
                    Dom._4eRemove(previousSibling[0].lastChild);
                }
            }
        }

        if (removeLastBr) {
            // Ignore bookmark nodes.(#3783)
            var bookmarkGuard = Walker.bookmark(FALSE, TRUE);

            var lastChild = $(block[0].lastChild);
            if (lastChild[0] && lastChild[0].nodeType === Dom.NodeType.ELEMENT_NODE && lastChild.nodeName() === 'br') {
                // Take care not to remove the block expanding <br> in non-IE browsers.
                if (UA.ie || lastChild.prev(bookmarkGuard, 1) ||
                    lastChild.next(bookmarkGuard, 1)) {
                    lastChild.remove();
                }
            }
        }

        // Get a reference for the next element. self is important because the
        // above block can be removed or changed, so we can rely on it for the
        // next interation.
        if (!self._.nextNode) {
            self._.nextNode = (isLast || block.equals(lastNode)) ? NULL :
                block._4eNextSourceNode(TRUE, NULL, lastNode);
        }

        return block;
    }
});

/**
 * get iterator for range
 * @member KISSY.Editor.Range
 * @returns {KISSY.Editor.Iterator}
 */
KERange.prototype.createIterator = function () {
    return new Iterator(this);
};

module.exports = Iterator;